package com.huaxin.dss2.data.NewSmallStation.fbox;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.huaxin.dss2.TokenBean;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;

import java.io.Closeable;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.BeanUtils;

/**
 * token管理类
 */
@Slf4j
public class TokenManager implements Closeable {
    private final CredentialProvider credentialProvider;
    private final Object tokenLock;
    private final JSON json;
    private final Logger logger;
    private CloseableHttpClient http;
    private final String idSvrUrl;

    public TokenManager(CredentialProvider credentialProvider, LoggerFactory loggerFactory) {
        this(credentialProvider, "https://account.flexem.com/core/", loggerFactory);
    }

    public TokenManager(CredentialProvider credentialProvider, String idSvrUrl, LoggerFactory loggerFactory) {
        this(credentialProvider, idSvrUrl, null, loggerFactory);
    }

    public TokenManager(CredentialProvider credentialProvider, String idSvrUrl, CloseableHttpClient http, LoggerFactory loggerFactory) {
        this.logger = loggerFactory.createLogger(this.getClass());
        this.idSvrUrl = idSvrUrl;
        this.credentialProvider = credentialProvider;
        this.tokenLock = new Object();
        this.http = http;
        if (http == null) {
            this.http = HttpClients.createDefault();
        }
        this.json = new JSONObject();
    }

    private String accessToken;

    public String getOrUpdateToken(String currentToken) throws IOException {
        if (currentToken == null) {
            String at = accessToken;
            if (at != null)
                return at;
        }

        synchronized (tokenLock) {
            String at = accessToken;
            if (at != null && !Objects.equals(at, currentToken)) {
                return at;
            }

            at = fetchToken();
            accessToken = at;
            return at;
        }
    }

    private String fetchToken() throws IOException {
        ClientCredential clientCredential = credentialProvider.getClientCredential();
        UserCredential userCredential = credentialProvider.getUserCredential();

        HttpPost request = new HttpPost(idSvrUrl + "connect/token");
        List<NameValuePair> formParams = new ArrayList<>();
        formParams.add(new BasicNameValuePair("username", userCredential.getUserName()));
        formParams.add(new BasicNameValuePair("password", userCredential.getPassword()));
        formParams.add(new BasicNameValuePair("scope", "openid offline_access fbox email profile"));
        formParams.add(new BasicNameValuePair("client_id", clientCredential.getClientId()));
        formParams.add(new BasicNameValuePair("client_secret", clientCredential.getClientSecret()));
        formParams.add(new BasicNameValuePair("grant_type", "password"));
        request.setEntity(new UrlEncodedFormEntity(formParams));
        try (CloseableHttpResponse response = http.execute(request)) {
            StatusLine statusLine = response.getStatusLine();
            HttpEntity entity = response.getEntity();
            int statusCode = statusLine.getStatusCode();
            // the following status code range mostly caused by incorrect credential.
            if (statusCode >= HttpStatus.SC_BAD_REQUEST && statusCode < HttpStatus.SC_INTERNAL_SERVER_ERROR && statusCode != 429) {
                throw new LoginFailedException(
                    statusLine.getStatusCode(),
                    statusLine.getReasonPhrase());
            }
            if (statusCode >= 300) {
                throw new HttpResponseException(statusCode, statusLine.getReasonPhrase());
            }
            if (entity == null) {
                throw new ClientProtocolException("Response contains no content");
            }
            ContentType contentType = ContentType.getOrDefault(entity);
            Charset charset = contentType.getCharset();
            String en = EntityUtils.toString(entity,charset);
            BeanUtils.copyProperties(JSON.toJavaObject(JSON.parseObject(en), TokenBean.class),TokenBean.getSingleton());
            String at = TokenBean.getSingleton().getAccessToken();
            this.logger.logTrace("Fetched new token.");
            log.info("Fetched new token.");
            return at;
        }
    }

    @Override
    public void close() throws IOException {
        if (this.http != null) {
            (this.http).close();
        }
    }
}
